// The MIT License (MIT)
// Copyright (c) 2011 Akiyoshi Ochiai
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to 
// deal in the Software without restriction, including without limitation the 
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is 
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// 

#include <vector>
#include <string>
#include <iostream>
#include <exception>
#include <stdexcept>

#include <ros/ros.h>
#include <ros/package.h>

#include <boost/thread.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>

#include <iconv.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>

#include "julius_driver.h"
#include <speech_recog_julius/WordWithConfidence.h>
#include <speech_recog_julius/SentenceWithScore.h>
#include <speech_recog_julius/SpeechRecogResult.h>
#include <std_msgs/Time.h>


namespace speech_recog_julius {

class SpeechRecogNode : private boost::noncopyable, public JuliusEventHandler {
 public:
  SpeechRecogNode();
  virtual ~SpeechRecogNode() {}

  void Run();

  virtual void OnSpeechReady();
  virtual void OnRecognitionSuccess(NBestSentences const& sentences);
  virtual void OnRecognitionFailure(std::string const& message);
  virtual void OnError(); // Must have a no-throw guarantee.
 private:
  void SpinThread();
  boost::shared_ptr<boost::thread> spin_thread_;
  boost::shared_ptr<ros::NodeHandle> node_;
  boost::shared_ptr<JuliusDriver> driver_;
  ros::Publisher result_pub_;
};


SpeechRecogNode::SpeechRecogNode() {
  node_.reset(new ros::NodeHandle());
  std::string config_path;
  if (!ros::param::get("~config_path", config_path)) {
    std::string path_to_this_pkg = ros::package::getPath("speech_recog_julius");  
    if (path_to_this_pkg.empty())
      path_to_this_pkg = ".";
    config_path = path_to_this_pkg + "/third_party/dictation-kit-v4.1/fast.jconf";
  }
  result_pub_ = node_->advertise<SpeechRecogResult>("/speech", 1);

  driver_.reset(new JuliusDriver(config_path));
  driver_->RegisterHandler(this);
}


void SpeechRecogNode::Run() {
  // Launch ROS event loop thread.
  boost::function<void()> fn = boost::bind(&SpeechRecogNode::SpinThread, this);
  spin_thread_ = boost::make_shared<boost::thread>(fn);

  driver_->RunLoop();

  spin_thread_->join();
}


void SpeechRecogNode::OnSpeechReady() {
  std::cout << "<<< please speak >>>" << std::endl;
}


void SpeechRecogNode::OnRecognitionSuccess(NBestSentences const& sentences) {
  SpeechRecogResult msg;
  std::cout << "--------" << std::endl;
  for (NBestSentences::const_iterator sentence_iter = sentences.begin();
       sentence_iter != sentences.end(); ++sentence_iter) {
    SentenceWithScore sentence;
    for (WordVector::const_iterator word_iter = sentence_iter->words().begin();
         word_iter != sentence_iter->words().end(); ++word_iter) {
      WordWithConfidence word;
      word.word = word_iter->word();
      word.confidence = word_iter->confidence();
      sentence.words.push_back(word);
    }
    sentence.score = sentence_iter->score();
    sentence.acoustic_model_score = sentence_iter->acoustic_model_score();
    sentence.language_model_score = sentence_iter->language_model_score();
    std::cout << "sentence[" << sentence_iter->score() << "] "
              << sentence_iter->JoinWords() << std::endl;
    msg.sentences.push_back(sentence);
  }
  std::cout << "--------" << std::endl;
  result_pub_.publish(msg);
}


void SpeechRecogNode::OnRecognitionFailure(std::string const& message) {
  ROS_WARN("%s", message.c_str());
}


// Must have a no-throw guarantee.
void SpeechRecogNode::OnError() {
  ROS_FATAL("Unhandled fatal error occured in Julius!");
  ros::shutdown();  
}


void SpeechRecogNode::SpinThread() {
  // Block SIGINT to use default handlers installed in ROS threads.
  // TODO: Confirm that this is really requried or not.
  //       Should I also consider Julius's AD input thread?
  ::sigset_t signal_set;
  ::sigemptyset(&signal_set);
  ::sigaddset(&signal_set, SIGINT);
  ::pthread_sigmask(SIG_BLOCK, &signal_set, NULL);

  ros::spin();
  driver_->Shutdown();
}

} // end namespace speech_recog_julius

int main(int argc, char** argv) {
  try {
    ros::init(argc, argv, "speech_recog_julius");
    speech_recog_julius::SpeechRecogNode node;
    node.Run();
  } catch (std::exception const& e) {
    ROS_FATAL("%s", e.what());
    return 1;
  } catch (...) {
    ROS_FATAL("Unknown exception!");
    return 1;
  }
  return 0;
}
